#include "banking_system.hh"
#include <iostream>
#include <sstream>
#include <iomanip>
#include <ctime>
#include <random>
#include <algorithm>  // the find function
#include <fstream>

namespace MiniBank
{

    std::string BankingSystem::generateUniqueAccountID(const std::string& accountType)
    {
        static std::vector<std::string> usedIDs;
        std::random_device rd;  // Random seed
        std::mt19937 gen(rd()); // Random number generator
        std::uniform_int_distribution<> dis(1000, 9999); // Generate a random number between 1000 and 9999

        std::string head;
        if (accountType == "Savings")
        {
            head = "6221";
        }
        else if (accountType == "Checking")
        {
            head = "6222";
        }

        std::string newID;
        do
        {
            std::stringstream ss;
            ss << head << "-" << dis(gen) << "-" << dis(gen) << "-" << dis(gen);
            newID = ss.str();
        } while (std::find(usedIDs.begin(), usedIDs.end(), newID) != usedIDs.end());  // Prevent duplicate IDs, find function returns end if not found

        usedIDs.push_back(newID);
        return newID;
    }

    void BankingSystem::registerCustomer(const std::string& name, double initialBalance_saving, double initialBalance_checking)
    {
        std::string savingsID = generateUniqueAccountID("Savings");
        std::string checkingID = generateUniqueAccountID("Checking");
        Customer v1 = Customer(name, savingsID, checkingID, initialBalance_saving, initialBalance_checking);
        customers.push_back(v1);
        std::cout << "Customer " << name << " successfully registered!" << std::endl;
        std::cout << "Two bank accounts have been created:" << std::endl;
        std::cout << "- Savings Account: " << savingsID << std::endl;
        std::cout << "- Checking Account: " << checkingID << std::endl;
    }

    void BankingSystem::accessCustomerAccounts(const std::string& name)
    {
        auto it = std::find_if(customers.begin(), customers.end(), NameMatcher(name));

        if (it != customers.end())
        {
            std::cout << "===== Customer: " << name << " =====" << std::endl;
            it->displayAccounts();
            while (true)
            {
                std::cout << "Options:" << std::endl;
                std::cout << "1. Access Savings Account" << std::endl;
                std::cout << "2. Access Checking Account" << std::endl;
                std::cout << "3. View all transactions for this customer" << std::endl;
                std::cout << "4. Generate customer report" << std::endl;
                std::cout << "5. Return to main menu" << std::endl;

                int choice;
                choice = GetChoice(1, 5);

                switch (choice)
                {
                case 1:
                {
                    SavingsAccount& savings = it->getSavingsAccount();
                    std::cout << "===== Bank Account: " << savings.getAccountID() << " (Savings Account) =====" << std::endl;
                    savings.displayInfo();
                    while (true)
                    {
                        std::cout << "1. Transfer" << std::endl;
                        std::cout << "2. View transaction history" << std::endl;
                        std::cout << "3. Generate bank account report" << std::endl;
                        std::cout << "4. Return to customer page" << std::endl;
                        int subChoice;
                        subChoice = GetChoice(1, 4);

                        switch (subChoice)
                        {
                        case 1:
                        {
                            CheckingAccount& target = it->getCheckingAccount();
                            Transfer_savings(target, savings);
                            break;
                        }
                        case 2:
                        {
                            View_transaction_history_savings(savings);
                            break;
                        }
                        case 3:
                        {
                            Generate_bank_account_report_savings(savings);
                            break;
                        }
                        case 4:
                            break;
                        }
                        if (subChoice == 4) break;
                    }
                    break;
                }
                case 2:
                {
                    CheckingAccount& checking = it->getCheckingAccount();
                    std::cout << "===== Bank Account: " << checking.getAccountID() << " (Checking Account) =====" << std::endl;
                    checking.displayInfo();
                    while (true)
                    {
                        std::cout << "1. Deposit" << std::endl;
                        std::cout << "2. Withdraw" << std::endl;
                        std::cout << "3. Transfer" << std::endl;
                        std::cout << "4. View transaction history" << std::endl;
                        std::cout << "5. Generate bank account report" << std::endl;
                        std::cout << "6. Return to customer page" << std::endl;

                        int subChoice;
                        subChoice = GetChoice(1, 6);

                        switch (subChoice)
                        {
                        case 1:
                        {
                            Deposit(checking);
                            break;
                        }
                        case 2:
                        {
                            Withdraw(checking);
                            break;
                        }
                        case 3:
                        {
                            Transfer_checking(checking);
                            break;
                        }
                        case 4:
                        {
                            View_transaction_history_checking(checking);
                            break;
                        }
                        case 5:
                        {
                            Generate_bank_account_report_checking(checking);
                            break;
                        }
                        case 6:
                            break;
                        }
                        if (subChoice == 6) break;
                    }
                    break;
                }
                case 3:
                    it->viewAllTransactions();
                    break;
                case 4:
                    it->generateCustomerReport();
                    break;
                case 5:
                    return;
                }
            }
        }
        else
        {
            std::cout << "Customer not found." << std::endl;
        }
    }

    void BankingSystem::Transfer_savings(Account& target, SavingsAccount& savings)
    {
        std::string targetID;
        double amount;
        std::string note;
        std::cout << "Enter destination account ID: ";
        std::cin.ignore(1000, '\n');
        std::getline(std::cin, targetID);
        std::cout << "Enter amount to transfer: ";
        std::cin >> amount;
        std::cout << "Add note (optional): ";
        std::cin.ignore(1000, '\n');
        std::getline(std::cin, note);
        auto ID_holder = std::find_if(customers.begin(), customers.end(), IDMatcher(targetID));
        if (ID_holder == customers.end())
        {
            std::cout << "Account ID does not exist." << std::endl;
            return;
        }
        
        if (targetID.substr(0, 4) == "6221" || ID_holder->getName() != savings.getAccountHolder())
        {
            std::cout << "You can only transfer to your checking account." << std::endl;
            return;
        }
        savings.transfer(target, amount, note);
    }

    void BankingSystem::View_transaction_history_savings(SavingsAccount& savings)
    {
        const auto& transactions = savings.getTransactions();
        for (const auto& transaction : transactions)
        {
            std::cout << "Type: " << transaction.getType() << ", Amount: $" << transaction.getAmount()
                << ", Note: " << transaction.getNote() << ", ID: " << transaction.getTransactionID() << std::endl;
        }
    }

    void BankingSystem::Generate_bank_account_report_savings(SavingsAccount& savings)
    {
        std::time_t now = std::time(nullptr);
        std::tm* localTime = std::localtime(&now);
        std::stringstream ss;
        ss << "transactions_" << savings.getAccountID() << "_"
            << std::put_time(localTime, "%Y-%m-%d") << ".txt";
        std::string filename = ss.str();
        savings.generateReport(filename);
    }

    void BankingSystem::Deposit(CheckingAccount& checking)
    {
        double amount;
        std::string note;
        std::cout << "Enter amount to deposit: ";
        std::cin >> amount;
        std::cout << "Add note (optional): ";
        std::cin.ignore(1000, '\n');
        std::getline(std::cin, note);
        checking.deposit(amount, note);
    }

    void BankingSystem::Withdraw(CheckingAccount& checking)
    {
        double amount;
        std::string note;
        std::cout << "Enter amount to withdraw: ";
        std::cin >> amount;
        std::cout << "Add note (optional): ";
        std::cin.ignore(1000, '\n');
        std::getline(std::cin, note);
        checking.withdraw(amount, note);
    }

    void BankingSystem::Transfer_checking(CheckingAccount& checking)
    {
        std::string targetID;
        double amount;
        std::string note;
        std::cout << "Enter destination account ID: ";
        std::cin.ignore(1000, '\n');
        std::getline(std::cin, targetID);
        std::cout << "Enter amount to transfer: ";
        std::cin >> amount;
        std::cout << "Add note (optional): ";
        std::cin.ignore(1000, '\n');
        std::getline(std::cin, note);

        auto ID_holder = std::find_if(customers.begin(), customers.end(), IDMatcher(targetID));
        if (ID_holder == customers.end())
        {
            std::cout << "Account ID does not exist." << std::endl;
            return;
        }

        if (targetID.substr(0, 4) == "6221") // Savings
        {
            SavingsAccount& target = ID_holder->getSavingsAccount();
            checking.transfer(target, amount, note);
        }
        else // Checking
        {
            CheckingAccount& target = ID_holder->getCheckingAccount();
            checking.transfer(target, amount, note);
        }
    }

    void BankingSystem::View_transaction_history_checking(CheckingAccount& checking)
    {
        const auto& transactions = checking.getTransactions();
        for (const auto& transaction : transactions)
        {
            std::cout << "Type: " << transaction.getType() << ", Amount: $" << transaction.getAmount()
                << ", Note: " << transaction.getNote() << ", ID: " << transaction.getTransactionID() << std::endl;
        }
    }

    void BankingSystem::Generate_bank_account_report_checking(CheckingAccount& checking)
    {
        std::time_t now = std::time(nullptr);
        std::tm* localTime = std::localtime(&now);
        std::stringstream ss;
        ss << "transactions_" << checking.getAccountID() << "_"
            << std::put_time(localTime, "%Y-%m-%d") << ".txt";
        std::string filename = ss.str();
        checking.generateReport(filename);
    }

    void BankingSystem::displayAllAccounts() const
    {
        std::cout << "===== All Bank Accounts =====" << std::endl;

        // Display SavingsAccount
        std::cout << "SAVINGS ACCOUNTS:" << std::endl;
        int savingsCount = 0;
        double totalBalance = 0;
        for (auto customer : customers)
        {
            const SavingsAccount& savings = customer.getSavingsAccount();
            std::cout << ++savingsCount << ". ID: " << savings.getAccountID()
                << "  |  Owner: " << savings.getAccountHolder()
                << "  |  Balance: $" << savings.getBalance() << std::endl;
            totalBalance += savings.getBalance();
        }

        // Display CheckingAccount
        std::cout << "CHECKING ACCOUNTS:" << std::endl;
        int checkingCount = 0;
        for (auto customer : customers)
        {
            const CheckingAccount& checking = customer.getCheckingAccount();
            std::cout << ++checkingCount << ". ID: " << checking.getAccountID()
                << "  |  Owner: " << checking.getAccountHolder()
                << "  |  Balance: $" << checking.getBalance() << std::endl;
            totalBalance += checking.getBalance();
        }
        std::cout << "Total Accounts: " << savingsCount + checkingCount << std::endl;
        std::cout << "Total Customers: " << customers.size() << std::endl;
        std::cout << "Total Balance: $" << totalBalance << std::endl;
    }

    void BankingSystem::generateGlobalTransactionReport() const
    {
        std::time_t now = std::time(nullptr);
        std::tm* localTime = std::localtime(&now);
        std::stringstream ss;
        ss << "transactions_" << std::put_time(localTime, "%Y-%m-%d") << ".txt";
        std::string filename = ss.str();

        // Save all transaction reports in file
        std::ofstream file(filename);
        if (file.is_open())
        {
            file << "Global Transaction Report" << std::endl;
            file << "----------------------------------------" << std::endl;
            for (auto customer : customers)
            {
                const auto& savingsTransactions = customer.getSavingsAccount().getTransactions();
                const auto& checkingTransactions = customer.getCheckingAccount().getTransactions();

                for (const auto& transaction : savingsTransactions)
                {
                    file << "Savings Account (" << customer.getName() << ") - Type: " << transaction.getType()
                        << ", Amount: $" << transaction.getAmount()
                        << ", Note: " << transaction.getNote() << ", ID: " << transaction.getTransactionID() << std::endl;
                }
                for (const auto& transaction : checkingTransactions)
                {
                    file << "Checking Account (" << customer.getName() << ") - Type: " << transaction.getType()
                        << ", Amount: $" << transaction.getAmount()
                        << ", Note: " << transaction.getNote() << ", ID: " << transaction.getTransactionID() << std::endl;
                }
            }
            file.close();
            std::cout << "Report successfully generated as \"" << filename << "\"" << std::endl;
            std::cout << "All transactions have been saved in chronological order." << std::endl;
        }
        else
        {
            std::cout << "Unable to open file for report generation." << std::endl;
        }
    }

    int BankingSystem::GetChoice(int Left, int Right)
    {
        int choice;
        while (true)
        {
            std::cout << "Please choose: ";
            std::cin >> choice;
            if (std::cin.fail())
            {
                std::cin.clear();
                std::cin.ignore(1000, '\n');
                std::cout << "Invalid input, please try again!\n";
                continue;
            }
            if (choice < Left || choice > Right)
            {
                std::cout << "Invalid Number, please try again!\n";
                continue;
            }

            break;
        }
        return choice;
    }

    // Define a comparison function
    bool name_matches(const Customer& customer, const std::string& name)
    {
        return customer.getName() == name;
    }

    bool id_matches(Customer& customer, const std::string& targetID)
    {
        if (targetID.substr(0, 4) == "6221")
        {
            return customer.getSavingsAccount().getAccountID() == targetID;
        }
        else
        {
            return customer.getCheckingAccount().getAccountID() == targetID;
        }

    }

}
